<?php

namespace App\Service;

use Symfony\Component\DomCrawler\Crawler;
use App\Util\Taxonomy;
use App\Util\Topic;
use RecursiveArrayIterator;

class ContentParser
{
    /**@var array */
    private $taxonomiesMapping = [];

    /** @var array */
    private $stopWords;

    /** @var array */
    private $stopTags;

    public function __construct(TaxonomiesManager $taxonomiesManager, array $stopWords, array $stopTags)
    {
        $this->taxonomiesMapping = $taxonomiesManager->getTaxonomies();
        $this->stopWords = $stopWords;
        $this->stopTags = $stopTags;
    }

    /**
     * @param Crawler $crawler
     * @param array $ignoredTags
     * @return Topic
     */
    public function parseFromCrawler(Crawler $crawler, string $taxonomy = '')
    {
        $topic = new Topic();
        $title = '';
        if (($titleNode = $crawler->filterXPath('//title')->first()) ||
            ($titleNode = $crawler->filterXPath('//meta[@property="og:title"]/@content')->first())) {
            $title = $titleNode->text();
        }

        $title = preg_replace(['/\|.*/', '/\//', '/:/'], ['', '-', ', '], $title);
        $topic->setTitle($title);

        $description = '';
        foreach (['name' => 'description', 'property' => 'og:description'] as $descriptionAttrName => $descriptionAttrValue) {
            $descriptionNode = $crawler->filterXPath("//meta[@{$descriptionAttrName}='{$descriptionAttrValue}']/@content");
            if ($descriptionNode->count() > 0) {
                $description = $descriptionNode->text();
                break;
            }
        }

        $callbacks = [
            function ($tag) use ($title, $description) {
                return preg_match("/\b{$tag}\b/i", $title) || (!empty($description) && preg_match("/\b{$tag}\b/i", $description));
            },
            function ($tag) use (&$body) {
                return preg_match("/\b{$tag}\b/i", $body);
            }
        ];

        if ($taxonomy == 'ALL') {
            $taxonomiesMapping = $this->taxonomiesMapping;
        } elseif (key_exists($taxonomy, $this->taxonomiesMapping)) {
            $taxonomiesMapping[$taxonomy] = $this->taxonomiesMapping[$taxonomy];
            $topic->setCategories([new Taxonomy($taxonomy)]);
        } else {
            return $topic->setContent(empty($description) ? $this->cleanBody($this->getBody($crawler)) : $description);
        }

        do {
            reset($taxonomiesMapping);
            $matched = false;
            do {
                $category = key($taxonomiesMapping);
                $tags = current($taxonomiesMapping);
                /**@var Taxonomy[] $taxonomies */
                $taxonomies = [];
                $callback = current($callbacks);
                foreach ($tags as $tag) {
                    if (!empty($tag) && $callback(preg_quote($tag))) {
                        $taxonomies[] = new Taxonomy($tag);
                    }
                }
                if (!empty($taxonomies)) {
                    $topic->setCategories([new Taxonomy($category)]);
                    $topic->setTags($taxonomies);
                    $matched = true;
                }
            } while (!$matched && next($taxonomiesMapping));
            if (!$matched) {
                $body = $this->cleanBody($this->getBody($crawler));
            }
        } while (next($callbacks));
        return $topic->setContent(empty($description) ? $body : $description);
    }

    private function getBody(Crawler $crawler)
    {
        return $crawler->filterXPath('//html/body');
    }

    public function cleanBody(Crawler $crawler)
    {
        $node = $crawler->getNode(0);
        $body = $node->ownerDocument->saveXML($node);
        $xPathExpresion = [];
        foreach ($this->stopWords as $stopWord => $attrs) {
            foreach ($attrs as $attr) {
                $xPathExpresion[] = "contains(@$attr, '$stopWord')";
            }
        }
        $xPathExpresion = sprintf('//*[%s]', implode(' or ', $xPathExpresion));
        foreach ($crawler->filterXPath($xPathExpresion) as $node) {
            $body = str_replace($node->ownerDocument->saveXML($node), '', $body);
        }
        $xPathExpresion = [];
        foreach ($this->stopTags as $stopTag) {
            $xPathExpresion[] = "contains(name(), '$stopTag')";
        }
        $xPathExpresion = sprintf('//*[%s]', implode(' or ', $xPathExpresion));
        foreach ($crawler->filterXPath($xPathExpresion) as $node) {
            $body = str_replace($node->ownerDocument->saveXML($node), '', $body);
        }
        //$body = strip_tags($body, '<h1><h2><h3><h4><h5><h6><p><li><table><pre><code>');
        $body = strip_tags($body, '<p><li><table><pre><code>');
        return preg_replace(['/^[ \t]*[\r\n]+/m', '/<!--.+-->/Usm', '/(<[a-z]+[1-6]?)\s+[^>]+/sm'], ['', '', "$1"], trim($body));
    }
}
